use async_trait::async_trait;
use serde_json::Value;
use std::sync::Arc;

use my_rust_core::{
    security::jwt::MyUserDetails,
    shared::{app_config_service, date_helper, serde_json_helper::ExtractJsonValue, util},
};

use sea_orm::{
    ActiveModelTrait, ColumnTrait, Condition, DatabaseConnection, DbErr, EntityOrSelect,
    EntityTrait, IntoActiveModel, PaginatorTrait, QueryFilter, Set,
};

use chrono::Duration;

use crate::{entities, security::user_active_token_manager::UserCachedActiveTokenManager};

#[async_trait]
pub trait UserActiveTokenService: Send + Sync {
    /// Checks if this refresh token is still active. If the data does not exist means the token has been revoked.
    /// Refresh token does not expire so it will not be cached like access token.
    /// Because caching is for reducing database access during each api call where access token validation check is required.
    async fn is_active_refresh_token(
        &self,
        username: &str,
        refresh_token: &str,
    ) -> Result<bool, DbErr>;

    /// Updates or inserts the access token and refresh token by username and device info.
    /// This can track the same user login via different agent.
    /// If you want to revoke the token just need to delete this row.
    async fn update_tokens(
        &self,
        my_user_details: &MyUserDetails,
        ip_address: &str,
        device_info: &Value,
        system_info: &Value,
    ) -> Result<(), DbErr>;
}

pub struct UserActiveTokenServiceImpl {
    db: Arc<DatabaseConnection>,
    user_cached_active_token_manager: Arc<dyn UserCachedActiveTokenManager>,
}

impl UserActiveTokenServiceImpl {
    pub fn new(
        db: Arc<DatabaseConnection>,
        user_cached_active_token_manager: Arc<dyn UserCachedActiveTokenManager>,
    ) -> Self {
        UserActiveTokenServiceImpl {
            db,
            user_cached_active_token_manager,
        }
    }
}

#[async_trait]
impl UserActiveTokenService for UserActiveTokenServiceImpl {
    /// Checks if this refresh token is still active. If the data does not exist means the token has been revoked.
    /// Refresh token does not expire so it will not be cached like access token.
    /// Because caching is for reducing database access during each api call where access token validation check is required.
    async fn is_active_refresh_token(
        &self,
        username: &str,
        refresh_token: &str,
    ) -> Result<bool, DbErr> {
        let is_active = entities::prelude::UserActiveToken::find()
            .filter(
                Condition::all()
                    .add(entities::user_active_token::Column::Username.eq(username))
                    .add(entities::user_active_token::Column::RefreshToken.eq(refresh_token))
                    .add(entities::user_active_token::Column::Enabled.eq(true)),
            )
            .count(self.db.as_ref())
            .await?;

        Ok(is_active > 0)
    }

    /// Updates or inserts the access token and refresh token by username and device info.
    /// This can track the same user login via different agent.
    /// If you want to revoke the token just need to delete this row.
    async fn update_tokens(
        &self,
        my_user_details: &MyUserDetails,
        ip_address: &str,
        device_info: &Value,
        system_info: &Value,
    ) -> Result<(), DbErr> {
        let device_id: Option<String> = device_info.get_string_value("id");

        let active_token = entities::prelude::UserActiveToken::find()
            .select()
            .filter(
                Condition::all()
                    .add(
                        entities::user_active_token::Column::Username.eq(&my_user_details.username),
                    )
                    .add(match device_id {
                        Some(device_id) => {
                            entities::user_active_token::Column::DeviceId.eq(device_id)
                        }
                        None => entities::user_active_token::Column::DeviceId
                            .is_null()
                            .or(entities::user_active_token::Column::DeviceId.eq("")),
                    })
                    .add(entities::user_active_token::Column::Enabled.eq(true)),
            )
            .one(self.db.as_ref())
            .await?;

        let mut active_token = match active_token {
            Some(data) => data.into_active_model(),
            None => entities::user_active_token::ActiveModel {
                id: Set(util::generate_uuid().to_string()),
                username: Set(my_user_details.username.clone()),
                enabled: Set(true),
                date_created: Set(date_helper::get_current_naive_date_time()),
                last_updated: Set(date_helper::get_current_naive_date_time()),
                ..Default::default()
            },
        };

        active_token.account_group_id = Set(my_user_details.account_group_id.as_string());
        active_token.ip_address = Set(ip_address.to_owned());
        active_token.device_id = Set(device_info.get_string_value("id"));
        active_token.device_brand = Set(device_info.get_string_value("brand"));
        active_token.device_model = Set(device_info.get_string_value("model"));
        active_token.device_product_name = Set(device_info.get_string_value("productName"));
        active_token.device_system_name = Set(device_info.get_string_value("systemName"));
        active_token.device_system_version = Set(device_info.get_string_value("systemVersion"));
        active_token.operating_system = Set(system_info.get_string_value("operatingSystem"));

        active_token.operating_system_version =
            Set(system_info.get_string_value("operatingSystemVersion"));

        active_token.platform_version = Set(system_info.get_string_value("platformVersion"));
        active_token.app_version = Set(system_info.get_string_value("platformVersion"));
        active_token.expiration = Set(app_config_service::APP_CONFIG.jwt.expiration as i32);
        active_token.access_token = Set(my_user_details.access_token.clone());
        active_token.refresh_token = Set(my_user_details.refresh_token.clone());

        let now = date_helper::get_current_naive_date_time();
        let date_expired = now + Duration::seconds(my_user_details.expires_in as i64);
        active_token.date_generated = Set(now);
        active_token.date_expired = Set(date_expired);
        active_token.date_created = Set(now);
        active_token.last_updated = Set(now);

        if active_token.id.is_unchanged() {
            active_token.update(self.db.as_ref()).await?;
        } else {
            active_token.insert(self.db.as_ref()).await?;
        }

        self.user_cached_active_token_manager
            .reload_cached_user_active_tokens()
            .await;

        Ok(())
    }
}
